package modelo.mapa;

import java.util.ArrayList;

import modelo.fabricas.FabricaAbstractaDeHectarea;
import modelo.fabricas.FabricaDeHectareaDeAgua;
import modelo.fabricas.FabricaDeHectareaDeTerrenoLlano;
import modelo.hectareas.Hectarea;
import modelo.hectareas.HectareaDeAgua;
import modelo.interfaces.ConstruibleTerrestre;
import modelo.interfaces.Persistible;
import modelo.servicios.RutaPavimentada;

public class Mapa implements Persistible{
	
	private Hectarea[][] mapaDeHectareas;
	private static Mapa instance; 
	private ArrayList<HectareaDeAgua> listaDeAgua;
	private int cantidadFilas;
	private int cantidadColumnas;
	private double distanciaDiagonal;
	private ArrayList<RutaPavimentada> listadoDeRutasPavimentadasMarcadas; 
	
	private Mapa(Dimension unaDimension)
	{		
		this.cantidadColumnas = unaDimension.obtenerCantidadColumnas();
		this.cantidadFilas = unaDimension.obtenerCantidadFilas();
		this.mapaDeHectareas=new Hectarea[this.cantidadFilas][this.cantidadColumnas];
		this.listaDeAgua=new ArrayList<HectareaDeAgua>();
		this.llenarMatriz();
		this.distanciaDiagonal=Math.sqrt(this.cantidadFilas*this.cantidadFilas +this. cantidadColumnas*this.cantidadColumnas);
		this.listadoDeRutasPavimentadasMarcadas = new ArrayList<RutaPavimentada>();
	}
	
	public Hectarea[][] obtenerMatrizDeHectareas()
	{
		return mapaDeHectareas;
	}

	private void llenarMatriz()
	{		
		FabricaAbstractaDeHectarea fabricaHectareaDeAgua = new FabricaDeHectareaDeAgua();
		FabricaAbstractaDeHectarea fabricaHectareaDeTerrenoLlano = new FabricaDeHectareaDeTerrenoLlano();
		for (int i=0 ; i < this.cantidadFilas ; i++)
		{
			for (int j=0 ; j < this.cantidadColumnas ; j++)
			{				
				mapaDeHectareas[i][j] = fabricaHectareaDeTerrenoLlano.crearHectarea(new Ubicacion(i,j));
			}			

			mapaDeHectareas[i][this.cantidadColumnas/2] = fabricaHectareaDeAgua.crearHectarea(new Ubicacion(i,this.cantidadColumnas/2));
			this.listaDeAgua.add((HectareaDeAgua) mapaDeHectareas[i][this.cantidadColumnas/2]);

			mapaDeHectareas[i][this.cantidadColumnas/2] = fabricaHectareaDeAgua.crearHectarea(new Ubicacion(i,this.cantidadColumnas/2));	

		}
	}

	public static Mapa getInstance(Dimension unaDimension) 
	{
		if (instance == null)
		{
			
				instance = new Mapa(unaDimension);
			
		} 
		return instance;		
	}
	
	public static Mapa getInstance()
	{
		if (instance != null)
		{
			return instance;
		} 
		return null;
	}

	public Hectarea obtenerHectareaSegunUbicacion(Ubicacion unaUbicacion) 
	{
		return mapaDeHectareas[unaUbicacion.obtenerCoordenadaX()][unaUbicacion.obtenerCoordenadaY()];		
	}
	
	public int obtenerCantidadColumnas()
	{
		return this.cantidadColumnas;
	}
	
	public int obtenerCantidadFilas()
	{
		return this.cantidadFilas;
	}
	
	public static void limpiarMapa()
	{
		Mapa.getInstance().limpiarListas();
		for(int i=0;i<Mapa.getInstance().obtenerCantidadFilas();i++)
		{
			for(int j=0;j < Mapa.getInstance().obtenerCantidadColumnas(); j++)
			{
				(Mapa.getInstance().obtenerMatrizDeHectareas()[i][j])=null;
			}
		}
		Mapa.getInstance().llenarMatriz();
	}
	
	public static void crearUnaHectareaDeAgua(Ubicacion unaUbicacion)
	{
		int coordX = unaUbicacion.obtenerCoordenadaX();
		int coordY = unaUbicacion.obtenerCoordenadaY();
		FabricaDeHectareaDeAgua fabricaDeAgua = new FabricaDeHectareaDeAgua();
		Hectarea hectareaDeAgua = fabricaDeAgua.crearHectarea(unaUbicacion); 
		Mapa.getInstance().obtenerMatrizDeHectareas()[coordX][coordY] = hectareaDeAgua;
	}
	
	public double getDistanciaDiagonal() 
	{		
		return this.distanciaDiagonal;
	}
	
	public boolean estanConectados(Ubicacion ubicacionOrigen,Ubicacion ubicacionDestino)
	{
		boolean estanConectadas = false;
		this.desmarcarRutas();
		if(this.ubicacionesConectadasPorRutas(ubicacionOrigen, ubicacionDestino))
		{
			estanConectadas = true;
		}
		this.desmarcarRutas();
		return estanConectadas;
	}	

	private void desmarcarRutas() 
	{
		this.listadoDeRutasPavimentadasMarcadas.clear();
	}
	
	public boolean ubicacionesConectadasPorRutas(Ubicacion ubicacionOrigen,Ubicacion ubicacionDestino)
	{
		if(this.sonLimitrofes(ubicacionOrigen, ubicacionDestino))
		{
			return true;
		}		
		
		this.marcarQueYaSePasoPorRuta(ubicacionOrigen);
		
		ArrayList<Ubicacion> ubicacionesLimitrofes = this.obtenerUbicacionesLimitrofes(ubicacionOrigen);
		for(Ubicacion ubicacionPosibleDeRutaSiguiente : ubicacionesLimitrofes)
		{
			if(this.verificarSiEstanConectados(ubicacionPosibleDeRutaSiguiente,ubicacionDestino))
			{
				return true;
			}
		}		
		return false;		
	}
	
	private void marcarQueYaSePasoPorRuta(Ubicacion ubicacionDeLaRuta)
	{
		ConstruibleTerrestre construccionTerrestreActual = Mapa.getInstance().obtenerHectareaSegunUbicacion(ubicacionDeLaRuta).obtenerConstruccionTerrestre();
		if( (construccionTerrestreActual != null) && (construccionTerrestreActual.esTransitable()) )
		{
			RutaPavimentada rutaActual = (RutaPavimentada)construccionTerrestreActual;
			this.listadoDeRutasPavimentadasMarcadas.add(rutaActual);
		}
	}
	
	private boolean verificarSiEstanConectados(Ubicacion ubicacionPosibleDeRutaSiguiente, Ubicacion ubicacionDestino)
	{
		ConstruibleTerrestre construccionTerrestreSiguiente = Mapa.getInstance().obtenerHectareaSegunUbicacion(ubicacionPosibleDeRutaSiguiente).obtenerConstruccionTerrestre();
		if ((construccionTerrestreSiguiente!=null) && (construccionTerrestreSiguiente.esTransitable()))
		{
			//si entro es porque hay una ruta pavimentada en la nuevaUbicacion a donde ire
			RutaPavimentada rutaSiguiente = (RutaPavimentada)construccionTerrestreSiguiente;		
			if( (!this.listadoDeRutasPavimentadasMarcadas.contains(rutaSiguiente)) && (rutaSiguiente.obtenerVida()==100) )
			{
				if(this.ubicacionesConectadasPorRutas(ubicacionPosibleDeRutaSiguiente, ubicacionDestino))
				{
					return true;
				}
			}			
		}	
		return false;
	}
	
	public ArrayList<Ubicacion> obtenerUbicacionesLimitrofes(Ubicacion ubicacionOrigen)
	{
		ArrayList<Ubicacion> ubicacionesLimitrofes = new ArrayList<Ubicacion>();
		int numeroDeFilas = Mapa.getInstance().obtenerCantidadFilas();
		int numeroDeColumnas = Mapa.getInstance().obtenerCantidadColumnas();
		Ubicacion ubicacionLimitrofe;
		
		int coordXNueva = ubicacionOrigen.obtenerCoordenadaX()+1;
		int coordYNueva = ubicacionOrigen.obtenerCoordenadaY();		
		if( (coordXNueva >= 0) && (coordXNueva < numeroDeFilas) && (coordYNueva >= 0) && (coordYNueva < numeroDeColumnas) )
		{
			ubicacionLimitrofe = new Ubicacion(coordXNueva,coordYNueva);
			ubicacionesLimitrofes.add(ubicacionLimitrofe);
		}
		
		
		coordXNueva = ubicacionOrigen.obtenerCoordenadaX()-1;
		coordYNueva = ubicacionOrigen.obtenerCoordenadaY();
		if( (coordXNueva >= 0) && (coordXNueva < numeroDeFilas) && (coordYNueva >= 0) && (coordYNueva < numeroDeColumnas) )
		{
			ubicacionLimitrofe = new Ubicacion(coordXNueva,coordYNueva);
			ubicacionesLimitrofes.add(ubicacionLimitrofe);
		}

		coordXNueva = ubicacionOrigen.obtenerCoordenadaX();
		coordYNueva = ubicacionOrigen.obtenerCoordenadaY()+1;
		if( (coordXNueva >= 0) && (coordXNueva < numeroDeFilas) && (coordYNueva >= 0) && (coordYNueva < numeroDeColumnas) )
		{
			ubicacionLimitrofe = new Ubicacion(coordXNueva,coordYNueva);
			ubicacionesLimitrofes.add(ubicacionLimitrofe);
		}

		coordXNueva = ubicacionOrigen.obtenerCoordenadaX();
		coordYNueva = ubicacionOrigen.obtenerCoordenadaY()-1;
		if( (coordXNueva >= 0) && (coordXNueva < numeroDeFilas) && (coordYNueva >= 0) && (coordYNueva < numeroDeColumnas) )
		{
			ubicacionLimitrofe = new Ubicacion(coordXNueva,coordYNueva);
			ubicacionesLimitrofes.add(ubicacionLimitrofe);
		}
		
		return ubicacionesLimitrofes;
	}	

	public boolean sonLimitrofes(Ubicacion unaUbicacion, Ubicacion otraUbicacion) 
	{

		ArrayList<Ubicacion> ubicacionesLimitrofes = new ArrayList<Ubicacion>();
		ubicacionesLimitrofes = this.obtenerUbicacionesLimitrofes(unaUbicacion);
		for (Ubicacion ubicacionLimitrofe : ubicacionesLimitrofes)
		{
			if ( (ubicacionLimitrofe.obtenerCoordenadaX() == otraUbicacion.obtenerCoordenadaX()) && 
			(ubicacionLimitrofe.obtenerCoordenadaY() == otraUbicacion.obtenerCoordenadaY()) )
			{
				return true;
			}
		}
		return false;
	}

	public boolean construccionPerteneceARegion(Region region, Ubicacion ubicacion)
	{
		int coordXMinima = region.obtenerUbicacion().obtenerCoordenadaX();
		int coordYMinima = region.obtenerUbicacion().obtenerCoordenadaY();
		int coordXMaxima = region.obtenerDimension().obtenerCantidadFilas() + coordXMinima;
		int coordYMaxima = region.obtenerDimension().obtenerCantidadColumnas() + coordYMinima;
		if ((ubicacion.obtenerCoordenadaX() >= coordXMinima) && (ubicacion.obtenerCoordenadaX() <= coordXMaxima)
		&& (ubicacion.obtenerCoordenadaY() >= coordYMinima) && (ubicacion.obtenerCoordenadaY() <= coordYMaxima))
		{
			return true;
		}
		 
		return false;
	}
	
	public boolean ubicacionDentroDelMapa(Ubicacion unaUbicacion)
	{
		int coordXDeUbicacion = unaUbicacion.obtenerCoordenadaX();
		int coordYDeUbicacion = unaUbicacion.obtenerCoordenadaY();
		if( (coordXDeUbicacion >= 0) && (coordXDeUbicacion < this.obtenerCantidadFilas())
				&& (coordYDeUbicacion >= 0) && (coordYDeUbicacion < this.obtenerCantidadColumnas()) )
		{
			return true;
		}
		return false;
	}
	
	public boolean ubicacionEstaEnAlgunBorde(Ubicacion unaUbicacion)
	{
		int coordX = unaUbicacion.obtenerCoordenadaX();
		int coordY = unaUbicacion.obtenerCoordenadaY();
		if (this.ubicacionDentroDelMapa(unaUbicacion))
		{
			if ( (coordX == 0) || (coordX == (this.obtenerCantidadFilas()-1))
			||(coordY == 0) || (coordY == (this.obtenerCantidadColumnas()-1)) )
			{
				return true;
			}
		}
		return false;		
	}
	
	public Ubicacion obtenerUbicacionOpuesta(Ubicacion unaUbicacion)
	{
		Ubicacion ubicacionOpuesta;
		if (this.ubicacionEstaEnAlgunBorde(unaUbicacion))
		{
			int coordX = unaUbicacion.obtenerCoordenadaX();
			int coordY = unaUbicacion.obtenerCoordenadaY();
			if(coordX == (this.obtenerCantidadFilas()-1))
			{
				coordX = 0;
			}
			else
			{
				if(coordY == (this.obtenerCantidadColumnas()-1))
				{
					coordY = 0;
				}
				else
				{
					if(coordX == 0)
					{
						coordX = (this.obtenerCantidadFilas()-1);
					}
					else
					{
						if(coordY == 0)
						{
							coordY = (this.obtenerCantidadColumnas()-1);
						}
					}
				}
			}					
			ubicacionOpuesta = new Ubicacion(coordX,coordY);
			return ubicacionOpuesta;				
		}
		else
		{
			return null;
		}		
	}

	public ArrayList<HectareaDeAgua> obtenerListaDeAgua()
	{
		return this.listaDeAgua;
	}	

	public static void instanciar(Mapa mapa) {
		if(mapa!=null){
			instance=new Mapa(new Dimension(mapa.cantidadFilas,mapa.cantidadColumnas));			
		}
	}
	
	private void limpiarListas() 
	{
		this.listaDeAgua=new ArrayList<HectareaDeAgua>();
		this.listadoDeRutasPavimentadasMarcadas = new ArrayList<RutaPavimentada>();
	}

	public void remover(ConstruibleTerrestre construccion) {
		if(construccion.obtenerVida()==0){
			Mapa.getInstance().obtenerHectareaSegunUbicacion(construccion.obtenerUbicacion()).construccionTerrestre(null);
		}
	}

	@Override
	public ArrayList obtenerDatosPersistibles()
	{
		ArrayList<Object> elems=new ArrayList<Object>();
		elems.add(Mapa.getInstance().cantidadFilas);
		elems.add(Mapa.getInstance().cantidadColumnas);		
		return elems;
	}

	@Override
	public void poblar(ArrayList elementos) 
	{
		Mapa.instance=null;
		int x=(int) elementos.get(0);
		int y=(int) elementos.get(1);
		Mapa.getInstance(new Dimension(x,y));
	}

	public static Object poblarS(ArrayList cargar)
	{
		instance=new Mapa(new Dimension(10,10));
		instance.poblar(cargar);
		return instance;
	}

	public static void borrarMapa()
	{
		instance=null;		
	}
	
}